using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace RebelHeartEclipse { [Serializable] public class MapData { public string _mapName; public int _width; public int _height; public string[] _grid; // each row is a string of length == _width } public enum BaseGridCellID { //Int value must corrispond to order of GameObject _prefabGridCellObj. Default_Grass = 0, }; public enum BaseGridSurfaceCellID { //Int value must corrispond to order of GameObject[] _prefabCellSurfObjs. Default_Tall_Grass = 0, }; public struct BaseGridData { public int START_X; public int START_Y; public int X; public int Y; public float CELL_SIZE; }; public class BattleGroundGridGenerator : MonoBehaviour { [Header("Character Action Stage Obj")] [SerializeField] public CharacterActionStage _characterActionStage; [Header("JSON Map Dir")] [SerializeField] public string _mapFileName = "JSON Maps/TestMap01"; //.json NOT needed if located in Resources. private MapData _mapData; [Header("Grid Cell Materials")] [SerializeField] public Material[] _cellMaterials; [Header("Grid Cell Objs")] [SerializeField] public GameObject _prefabGridCellObj; public Dictionary> _baseGridCellObjDict; public GridCellObj[,] _baseGridCellObjCached = new GridCellObj[25, 25]; [Header("Cell Surface Objs")] [SerializeField] public GameObject[] _prefabCellSurfObjs; public Dictionary> _baseGridSurfaceCellObjDict; [Header("--------------------UI MANAGER PLUGIN")] [Header("Plugin the UI manager here.")] [SerializeField] public UIManager _UIManager; private Transform _transform; private Vector3 _curPosition, _startPosition, _baseStartPosition, _centerPoint, _surfCellObjOffset; private const int _maxBaseGridCellsX = 25; private const int _maxBaseGridCellsY = 25; private BaseGridData _baseGridData; private bool _initFieldGeneration; //https://pixie.haus/saved private void Awake() => Setup(); private void Start() { } private void Setup() { _transform = this.transform; _baseGridData.X = _maxBaseGridCellsX; _baseGridData.Y = _maxBaseGridCellsY; _baseGridData.CELL_SIZE = 0.501f;//0.5025f;//0.505f;// 0.5f; _initFieldGeneration = false; _startPosition = new Vector3(-10.0f, -0.66f, -10.0f); _baseStartPosition = _startPosition; PopulateBaseGridCellsDict(); PopulateSurfaceGridCellsDict(); InitGridMapFile(); InitGeneration(); //Call from camera to Init. We get the center point this way. //Resources.UnloadUnusedAssets(); //expensive. //GC.Collect(); } private void InitGridMapFile() { TextAsset jsonFile = Resources.Load(_mapFileName); if (jsonFile != null) { _mapData = JsonUtility.FromJson(jsonFile.text); } else { print("\nMap file not found: " + _mapFileName); } } public void InitGeneration() //Call from outside of this Class. { if (!_initFieldGeneration) { InitBaseGridGeneration(); _initFieldGeneration = true; } } private void InitBaseGridGeneration() { float cellWidth = GetBaseGridCellWidth(); float curFieldWidth = cellWidth * _baseGridData.X; for (int x = 0; x < _baseGridData.X; x++) { for (int y = 0; y < _baseGridData.Y; y++) { if (x == 0 && y == 0)//Start Position { _curPosition = _startPosition; _baseStartPosition = _startPosition; //_cellWidth = cellWidth; } else { _curPosition = _startPosition + new Vector3(x * _baseGridData.CELL_SIZE, 0, y * _baseGridData.CELL_SIZE); } if (x == _baseGridData.X / 2 && y == _baseGridData.Y / 2) { _centerPoint = _curPosition; } GameObject obj = GetBaseGridCellObj(BaseGridCellID.Default_Grass); if (obj == null) return; obj.transform.position = _curPosition; obj.SetActive(true); //CellObj class fires OnEnable to retrieve assigned grid position at this point. obj.GetComponent().SetCellPosition(x, y); //Position obtained when OnEnable() is fired. obj.GetComponent().PopulateSharedMaterialReference(_cellMaterials); _baseGridCellObjCached[x, y].PopulateSharedMaterialReference(_cellMaterials); _baseGridCellObjCached[x, y].PluginUIManager(ref _UIManager); ApplyGridCellTexturePattern(x, y); } } //ApplySurfaceGridObjs(); //We do this in ApplyGridCellTexturePattern() to avoid rendering grass on ever cell. } private void ApplyGridCellTexturePattern(int x, int y) { //*Omitted } private void ApplySurfaceGridObjs() { int cellStartX = 0; int cellStartY = 0; int cellEndX = _baseGridData.X; int cellEndY = _baseGridData.Y; BaseGridSurfaceCellID surfaceObjToApply = BaseGridSurfaceCellID.Default_Tall_Grass; for (int x = cellStartX; x < cellStartX + cellEndX; x++) { for (int y = cellStartY; y < cellStartY + cellEndY; y++) { ActivateSurfaceGridObj(_baseGridCellObjCached[x, y].GetCellPosition(), x, y, surfaceObjToApply); } } } private void ActivateSurfaceGridObj(Vector3 spawnPosition, int x, int y, BaseGridSurfaceCellID baseGridSurfaceCellID) { GameObject obj = GetBaseGridSurfaceCellsObject(baseGridSurfaceCellID); if (obj == null) return; _surfCellObjOffset = new Vector3(18.32f, 0.5f, 2.20f); obj.transform.position = spawnPosition + _surfCellObjOffset; obj.SetActive(true); GridCellSurfObj gridCellSurfObj = obj.GetComponent(); if (gridCellSurfObj == null) return; _baseGridCellObjCached[x, y].SetCellSurfaceObj(gridCellSurfObj); } private void PopulateBaseGridCellsDict() { if (_baseGridCellObjDict == null) _baseGridCellObjDict = new Dictionary>(); _baseGridCellObjDict.Add(BaseGridCellID.Default_Grass, new List()); for (int x = 0; x < _maxBaseGridCellsX; x++) { for (int y = 0; y < _maxBaseGridCellsX; y++) { AddBaseGridCellsObject(BaseGridCellID.Default_Grass, x, y); } } } private void AddBaseGridCellsObject(BaseGridCellID baseGridCellID, int x, int y) { //*Omitted } private GameObject GetBaseGridCellObject(BaseGridCellID baseGridCellID) { //*Omitted return null; } private float GetBaseGridCellWidth() { float width = 0f; if (_prefabGridCellObj != null) { width = _prefabGridCellObj.GetComponent().bounds.size.x; } return width; } private GameObject GetBaseGridCellObj(BaseGridCellID baseGridCellID) { //*Omitted return null; } private void PopulateSurfaceGridCellsDict() { if (_baseGridSurfaceCellObjDict == null) _baseGridSurfaceCellObjDict = new Dictionary>(); _baseGridSurfaceCellObjDict.Add(BaseGridSurfaceCellID.Default_Tall_Grass, new List()); for (int x = 0; x < _maxBaseGridCellsX; x++) { for (int y = 0; y < _maxBaseGridCellsY; y++) { AddBaseGridSurfaceCellsObject(BaseGridSurfaceCellID.Default_Tall_Grass); } } } private void AddBaseGridSurfaceCellsObject(BaseGridSurfaceCellID baseGridSurfaceCellID) { //*Omitted } private GameObject GetBaseGridSurfaceCellsObject(BaseGridSurfaceCellID baseGridSurfaceCellID) { //*Omitted return null; } public float GetCellWidth() { return _baseGridData.CELL_SIZE; } public Vector3 GetCenterGridPosition() => _centerPoint; public Vector3 GetGridStartPosition() => _baseStartPosition; public int GetGridCellTotalX() => _baseGridData.X; public int GetGridCellTotalY() => _baseGridData.Y; public GridCellObj GetGridCellObj(int x, int y) => _baseGridCellObjCached[x, y]; public Vector3 GetSpecifiedCellPosition(int x, int y) { //_pivotPoint = _battleGroundGridGenerator._baseGridCellObjCached[clickX, clickY].GetCellPosition(); return this._baseGridCellObjCached[x, y].GetCellPosition(); } public void ActivateMoveableCells(int centerX, int centerY, CharacterClass characterClass) { switch(characterClass) { case CharacterClass.Knight: ApplyKnightMovementCellPattern(centerX, centerY, true); break; case CharacterClass.Bowman: ApplyBowmanMovementCellPattern(centerX, centerY, true); break; } } public void DeactivateMoveableCells(int centerX, int centerY, CharacterClass characterClass) { //if (InvalidDeactivation()) return; switch (characterClass) { case CharacterClass.Knight: ApplyKnightMovementCellPattern(centerX, centerY, false); break; case CharacterClass.Bowman: ApplyBowmanMovementCellPattern(centerX, centerY, false); break; } } //Breadth-First Search BFS //Start from your origin (the player’s cell). //Visit all neighbors(adjacent cells). //Then visit the neighbors of those neighbors, and so on. //It always expands outward layer by layer, like ripples in water. private void ApplyKnightMovementCellPattern(int centerX, int centerY, bool activate) { int total = 9; // must be odd, 3, 5, 7, 9 Ect. int radius = total / 2; int width = _baseGridData.X; int height = _baseGridData.Y; _characterActionStage.PathFindingMovement.Clear(); //_pathFindingMovement = new Dictionary<(int, int), (int, int)>(); //2D array same size as grid to identify if visited or not, per cell. //As BFS explores through, we mark as visited. var visited = new bool[width, height]; //FIFO First in First out queue. //Why? to hold the cells BFS has explored so far. //We use tuple (ie data structure holding more than one part data) //too store grid cell x and y, as well as the current distance from //the start-begin cell. We can identify when the BFS has journied to our //set limit of reach. //Below our tuple, datat structure. var queue = new Queue<(int x, int y, int dist)>(); //Enter starting point data, ie distance is 0 because we are stood upon the start. queue.Enqueue((centerX, centerY, 0)); //Set start cell to visited, otherwise we will end up adding it again //later over and over. Unwanted behaviour this. visited[centerX, centerY] = true; //Main BFS loop. Runs until the FIFO queue is empty, ie we have explored ////all cells possible set within our limits. while (queue.Count > 0) { //FIFO so we are always exploring the closest cell first. //Distance 0, 1 and 2 ect. We unpack the tuple here from our queue. //x and y are our current cood grid cell being explored. //Then dequeue this as we will add the next (neighboring cell further along) to back of the queue. var (x, y, dist) = queue.Dequeue(); //Ensure out diamond shape movement in grid cells. (Manhattan distance) //Math.Abs(x - centerX) = how far horizontally we are from the starting point. //Math.Abs(y - centerY) = how far vertically we are. //Adding them = Manhattan distance. //If sum is greater than radius, the cell outside our diamond pattern, so skip. if (Math.Abs(x - centerX) + Math.Abs(y - centerY) > radius) continue; //Rivers not allowed. if (_baseGridCellObjCached[x, y].GetCurrentCellMatID() == GridCellMatID.Cell_Default_River) continue; if (activate) { _baseGridCellObjCached[x, y].ApplyCellType(GridCellMatID.Cell_Default_Moveable); _baseGridCellObjCached[x, y].ApplySurfaceType(GridCellMatID.Cell_Default_Moveable); } else { _baseGridCellObjCached[x, y].RevertInitCellType(); _baseGridCellObjCached[x, y].RevertInitSurfType(); } //Expand outward. foreach (var (nx, ny) in GetNeighbors(x, y)) { //Skip visited already cells and ensure we are within entire grid limits. if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; if (visited[nx, ny]) continue; //Flag our visited cell, so we don't revisit. visited[nx, ny] = true; //Save our movement for A* path finding-traveral. //_pathFindingMovement[(nx, ny)] = (x, y); _characterActionStage.PathFindingMovement[(nx, ny)] = (x, y); //nx and ny respectivley are neighbour x and y cell. //A neighbor is always one step further away, this distance plus one. //Add this to the back of the queue. //I.e We found a new valid cell at nx,ny and it’s one step further from ////where we just were. Save into the list of cells to explore, later in the queue. queue.Enqueue((nx, ny, dist + 1)); } } // Keep center cell normal _baseGridCellObjCached[centerX, centerY].RevertInitCellType(); _baseGridCellObjCached[centerX, centerY].RevertInitSurfType(); } private void ApplyBowmanMovementCellPattern(int centerX, int centerY, bool activate) { //*Omitted } private void ApplyBanditMovementCellPattern(int centerX, int centerY, bool activate) { //*Omitted } public void ActivateAttackableCells(int centerX, int centerY, CharacterClass characterClass) { switch (characterClass) { case CharacterClass.Knight: ApplyKnightAttackCellPattern(centerX, centerY, true); break; case CharacterClass.Bowman: ApplyBowmanAttackCellPattern(centerX, centerY, true); break; } } public void DeactivateAttackableCells(int centerX, int centerY, CharacterClass characterClass) { switch (characterClass) { case CharacterClass.Knight: ApplyKnightAttackCellPattern(centerX, centerY, false); break; case CharacterClass.Bowman: ApplyBowmanAttackCellPattern(centerX, centerY, false); break; } } //*Omitted //*Omitted //*Omitted //*Omitted //*Omitted //*Omitted } }